﻿using System;
using System.Linq;
using System.Collections.Generic;
using JpLabs.Extensions;
using JpLabs.CustomEvents;
using Duellum.Core.ExceptionHelper;

namespace Duellum.Core
{
	/// http://msdn2.microsoft.com/en-us/library/ms752914.aspx = Dependency Properties Overview
	/// http://msdn.microsoft.com/en-us/library/ms745795.aspx = Dependency Property Callbacks and Validation (and Coercion)
	/// http://msdn2.microsoft.com/en-us/library/ms753197.aspx = Property Value Inheritance
	/// http://msdn2.microsoft.com/en-us/library/ms749011.aspx = Attached Properties Overview
	/// http://msdn.microsoft.com/en-us/library/ms742806.aspx = Routed Events Overview
	
	//public struct DependentPropertyChangedEventArgs
	//{
	//    public object NewValue { get; }
	//    public object OldValue { get; }
	//    public DependentProperty Property { get; }
	//}
	//public delegate void PropertyChangedCallback(DependentObject d, DependentPropertyChangedEventArgs e);
	//public class PropMetadata
	//{
	//    public object DefaultValue { get; set; }
	//    //public PropertyChangedCallback PropertyChangedCallback { get; set; }
	//    public PropMetadata(object defaultValue)
	//    {
	//        DefaultValue = defaultValue;
	//    }
	//}
	
	[Flags]
	public enum AugPropertyFlags
	{
		None = 0,
		DoesNotInherit = 1
	}

	public class AugValueChangedEventArgs : EventArgs
	{
		public delegate void EventHandler(AugObject sender, AugValueChangedEventArgs evArgs);
		
		public object NewValue { get; private set; }
		public object OldValue { get; private set; }
		public AugProperty Property { get; private set; }
		
		public AugValueChangedEventArgs(AugProperty property, object oldValue, object newValue)
		{
			this.Property = property;
			this.OldValue = oldValue;
			this.NewValue = newValue;
		}
	}

	public class CancellableAugValueChangedEventArgs : AugValueChangedEventArgs //seealso: System.ComponentModel.CancelEventArgs
	{
		public bool Cancel { get; set; }
		
		public CancellableAugValueChangedEventArgs(AugProperty property, object oldValue, object newValue)
			: base(property, oldValue, newValue)
		{}
	}
	
	public sealed class AugProperty : IEquatable<AugProperty>
	{
		static public readonly object UnsetValue = new object();
		
		private IAugPropertyBehavior defaultBehavior;
		private IList<KeyValuePair<Type,IAugPropertyBehavior>> behaviorList; //TODO: use this
		
		private TypeConverter propertyTypeConverter;
		//[Obsolete]private object DefaultValue;
		//System.Collections.ObjectModel.ReadOnlyCollection<>
		
		public string Name { get; private set; }
		public Type OwnerType { get; private set; }
		public Type PropertyType { get; private set; }
		public AugPropertyFlags Flags { get; private set; }
		
		//public bool ReadOnly { get; private set; }
		//public Func<object,bool> ValidateValueCallback { get; private set; }
		//public Func<object,bool> OnSetCoerceValueCallback { get; private set; }
		//public Func<object,bool> OnGetCoerceValueCallback { get; private set; }
		
		public bool Inherits { get { return (Flags & AugPropertyFlags.DoesNotInherit) == 0; } }
		
		private readonly string toString;
		private readonly int hashCode;

		private AugProperty(string name, Type propType, Type ownerType, AugPropertyFlags flags, IAugPropertyBehavior defaultBehavior)
		{
			Name = name;
			PropertyType = propType;
			OwnerType = ownerType;
			this.defaultBehavior = defaultBehavior;
			Flags = flags;
			
			propertyTypeConverter = new TypeConverter(PropertyType);			
			toString = string.Concat(OwnerType.FullName, "*", Name);
			hashCode = toString.GetHashCode();
		}

		static public AugProperty Create(string name, Type propType, Type ownerType)
		{
		    return Create(name, propType, ownerType, AugPropertyFlags.None, null);
		}

		static public AugProperty Create(string name, Type propType, Type ownerType, object defaultValue)
		{
			IAugPropertyBehavior behavior = BehaviorFromDefaultValue(defaultValue, propType);
			
			return Create(name, propType, ownerType, AugPropertyFlags.None, behavior);
		}

		static public AugProperty Create(string name, Type propType, Type ownerType, AugPropertyFlags flags, IAugPropertyBehavior defaultBehavior)
		{
			if (defaultBehavior == null) defaultBehavior = new SimpleAugPropBehavior(propType.GetDefaultValue());
			
			return new AugProperty(name, propType, ownerType, AugPropertyFlags.None, defaultBehavior);
		}
		
		static private IAugPropertyBehavior BehaviorFromDefaultValue(object defaultValue, Type propType)
		{
			if (defaultValue is IAugPropertyBehavior) return (IAugPropertyBehavior)defaultValue;
			
			if (!(defaultValue is IAugValueFactory)
			&&  !(defaultValue is IAugValue)
			&&  !propType.IsAssignableFromValue(defaultValue)) throw Error.ArgumentOfIncorrectType(propType, defaultValue, "defaultValue");

			return new SimpleAugPropBehavior(defaultValue);
		}

		public IAugPropertyBehavior GetDefaultBehavior()
		{
			return this.defaultBehavior;
		}

		public IAugPropertyBehavior GetBehavior(Type forType)
		{
			if (behaviorList != null) {
				//return first that is bigger or equal to requested type
				foreach (var kvPair in behaviorList) {
					if (kvPair.Key.IsBiggerOrEqual(forType)) return kvPair.Value;
				}
			}
			//or use default
			return this.defaultBehavior;
		}

		//internal object GetDefaultValue(Type forType)
		//{
		//    return ConvertToPropertyType(this.GetBehavior(forType).GetDefaultValue());
		//}

		//public bool IsValidValue(object value)
		//{
		//    //TODO: Test value type
		//    //TODO: Call ValidateValue callback, if any
		//    throw new NotImplementedException();
		//}
		//public object CoerceValue(object obj, object value)
		//{
		//    throw new NotImplementedException();
		//}
		
		public void OverrideBehavior(Type forType, IAugPropertyBehavior typeBehavior)
		{
		    //TODO: implement this: some of the augProperty behavior may be overridden for specific types
		    throw new NotImplementedException();
		}
		
		public override bool Equals(object obj)
		{
			if (obj is AugProperty) return Equals((AugProperty)obj);
			return false;
		}

		public bool Equals(AugProperty other)
		{
			//(this.OwnerType.Equals(other.OwnerType) && this.Name == other.Name);
			//return (toString == other.toString); //(hashCode == other.GetHashCode());
			return Name == other.Name && OwnerType == other.OwnerType;
		}

		public override int GetHashCode()
		{
			return hashCode;
		}

		public override string ToString()
		{
			return toString;
		}

		public object ConvertToPropertyType(object value)
		{
			if (value is IAugValueFactory) throw new ArgumentException(); //value = ((IAugValueFactory)value).GetValue();
			
			if (value is IAugValue) return value;

			//return ConvertTo(value, this.PropertyType);
			return propertyTypeConverter.Convert(value);
		}

		//private static object ConvertTo(object value, Type type)
		//{
		//    //TODO: use the class below (TypeConverter)
			
		//    if (type == null) throw new ArgumentNullException("type");
			
		//    //Convert.ChangeType() does not work with Nullable (e.g. DateTime?, int?)
		//    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
		//    {
		//        //TODO: optimize calls to this function so that this type convertion is not repeated
		//        type = type.GetGenericArguments()[0];
		//    }
			
		//    //Try light conversion
		//    if (typeof(IConvertible).IsAssignableFrom(type)) return Convert.ChangeType(value, type); //might throw a FormatException
			
		//    //Check if casting is possible
		//    if (value == null || !type.IsAssignableFrom(value.GetType())) return null;
			
		//    return value;
		//}
	}

	public interface ITypeConverter
	{
		object Convert(object value);
		object Convert(object value, System.Globalization.CultureInfo cultureInfo);
	}
	
	public static class TypeConverter<TTargetType>
	{
		public static TypeConverter Converter = new TypeConverter(typeof(TTargetType));
	}

	public class TypeConverter
	{
		//TODO: research TypeConverterAttribute and ValueSerializerAttribute

		private readonly Type targetType;
		private readonly bool isTargetTypeConvertible;
		
		public TypeConverter(Type type)
		{
			if (type == null) throw new ArgumentNullException("type");
			
			targetType = type;
			
			//Convert.ChangeType() does not work with Nullable (e.g. DateTime?, int?)
			if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
			{
				targetType = targetType.GetGenericArguments()[0];
			}
			
			isTargetTypeConvertible = typeof(IConvertible).IsAssignableFrom(type);
		}

		public object Convert(object value)
		{
			return Convert(value, System.Threading.Thread.CurrentThread.CurrentCulture);
		}
		
		public object Convert(object value, System.Globalization.CultureInfo cultureInfo)
		{
			//Try light conversion
			if (isTargetTypeConvertible) return System.Convert.ChangeType(value, targetType, cultureInfo); //might throw a FormatException
			
			//Try equivalent to: return value as Type;
			if (value == null || !targetType.IsAssignableFrom(value.GetType())) return null;
			return value;
		}
	}
}
